﻿using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using Components;

namespace PhpVH.Tests.Integration
{
    [TestFixture(Category = "VulnerabilityScanner")]
    public class VulnerabilityScannerTests
    {
        private const string _fileNotMoved = "A file ({0}) was not moved using move_uploaded_file.";

        private const string _fileNotUploaded = "A file ({0}) was not uploaded.";

        private const string _invalidUpload = "Invalid or malformed upload";

        private const string _commandMissed = "Commmand injection vulnerability missed in {0}.";

        private const string _arbitraryRead = "Arbitrary read";

        private const string _localFileInclusion = "Local file inclusion";

        private const string _missed = "Vulnerability missed";

        private const string _noAlertForTest = "No alert was raised for the PHP test {0}. This generally means a vulnerability went undetected by the scanner.";

        private const string _falsePositiveMarkerFound = "False positive marker found in alert. This generally means a test detected a vulnerability where there was none.";

        private const string _falsePositive = "False positive";

        private void RunVulnerabilityTest(string name, string mode, Action<PhpVHTester> action)
        {
            var phpFiles = Directory.GetFiles("PHP\\" + name);
            using (var tester = new PhpVHTester(name, mode, phpFiles))
            {
                try
                {
                    tester.RunPhpVH();
                    action(tester);
                    var alerts = tester.LoadAlerts();

                    phpFiles
                        .Select(x => "/" + Path.GetFileName(x))
                        .Iter(x => Assert.IsTrue(alerts.Any(y => y.Trace.Request.Contains(x)), _noAlertForTest, x));

                    var anyFalsePositives = alerts.Any(x => x.Trace.Request.Contains("/FalsePositiveCheck.php"));
                    Assert.IsFalse(anyFalsePositives, _falsePositiveMarkerFound);
                }
                catch
                {
                    tester.DumpOutput();

                    throw;
                }
            }
        }

        private void RunVulnerabilityTest(string name, string mode)
        {
            RunVulnerabilityTest(name, mode, x => { });
        }

        private bool HasBeenMoved(ScanAlertCollection scanAlerts, string filename)
        {
            return scanAlerts
                .Any(x => x.Trace.Calls
                    .Any(y =>
                        y.Name == "move_uploaded_file" &&
                        y.ParameterValues.Count > 1 &&
                        y.ParameterValues[1] == filename));
        }

        private void TestUploadedFile(PhpVHTester tester, ScanAlertCollection alerts, string filename)
        {
            Assert.IsTrue(HasBeenMoved(alerts, filename), _fileNotMoved, filename);
            var shellFile = Path.Combine(tester.TestDirectory.FullName, filename);
            Assert.IsTrue(File.Exists(shellFile), _fileNotUploaded, shellFile);
            var shellFileText = File.ReadAllText(shellFile);
            var isValidShellFile = shellFileText.Contains("system") || shellFileText.Contains("AddType");
            Assert.IsTrue(isValidShellFile, _invalidUpload);
        }

        [Test(Description = "Arbitrary upload test")]
        public void ArbitraryUploadTestMethod()
        {
            RunVulnerabilityTest(
                "ArbitraryUpload",
                "F",
                tester =>
                {
                    var alerts = tester.LoadAlerts();

                    new[] { "shell.php", "shell.php.gif", "shell.php.jpg", ".htaccess", }
                        .Iter(y => TestUploadedFile(tester, alerts, y));
                });
        }

        [Test(Description = "Command injection test")]
        public void CommandInjectionTestMethod()
        {
            RunVulnerabilityTest("CommandInjection", "C");
        }

        private void ReadTestCore(PhpVHTester tester, string anchor)
        {
            Assert.IsTrue(
                tester
                    .LoadAlerts()
                    .All(x => x.Trace.Response.Contains(anchor)),
                _falsePositive);
        }

        [Test(Description = "Arbitrary read test")]
        public void ArbitraryReadTestMethod()
        {
            RunVulnerabilityTest(
                "ArbitraryRead",
                "L",
                tester => ReadTestCore(tester, "LFI_Test123<?php"));
        }

        [Test(Description = "Local file inclusion test")]
        public void LocalFileInclusionTestMethod()
        {
            RunVulnerabilityTest(
                "LocalFileInclusion",
                "L",
                tester => ReadTestCore(tester, "LFI_Test1235230"));
        }

        [Test(Description = "PHP injection test")]
        public void PhpInjectionTestMethod()
        {
            RunVulnerabilityTest("PhpInjection", "P");
        }

        [Test(Description = "Cross-site scripting test test")]
        public void XssTestMethod()
        {
            RunVulnerabilityTest("Xss", "X");
        }

        [Test(Description = "Reflection test")]
        public void DynamicTestMethod()
        {
            RunVulnerabilityTest("DynamicInvocation", "D");
        }

        [Test(Description = "SQL injection test")]
        public void SqlInjectionTestMethod()
        {
            RunVulnerabilityTest("SqlInjection", "S");
        }

        [Test(Description = "Open redirect test")]
        public void OpenRedirectTestMethod()
        {
            RunVulnerabilityTest("OpenRedirect", "R");
        }
    }
}
